# -*- coding: utf-8 -*-
# 姓名:谚语
# 开发时间:2022/11/11 20:23
from collections import deque
from datetime import datetime

from dateutil.relativedelta import relativedelta
from flask import current_app
from flask_restful import Resource
from sqlalchemy import or_

from comment.modules import db
from comment.modules.debt_info import Debt_info
from comment.modules.expected_return import Expected_return
from comment.modules.funding_not_matched import Funding_not_matched
from comment.modules.matched_result import Matched_result
from comment.utils.gen_trans_id import gen_trans_id
from financial.resources.transaction.const import DealType


class MatchQueue:
    """
    采用deque来构建一个栈。禁用deque的右端，只是用左端
    """

    def __init__(self, item_list):
        """初始化栈"""
        self.items = deque(item_list)

    # 从栈中取一条待匹配的数据
    def get_item(self):
        return self.items.popleft()

    # 将一条即将待匹配的数据放入栈中
    def push(self, item):
        return self.items.appendleft(item)

    # 判断栈中的数据是否为空
    @property  # 将函数定义为属性  属性调用不需要()
    def empty(self):
        return len(self.items) == 0


class Match(object):
    """
    正式开始撮合匹配资金
    """

    def __init__(self):
        """初始化, 把所有待匹配的债权和待匹配的资金都从数据库中取出来"""
        # 查询未匹配或者部分匹配的债权列表    order_by(按照什么排序.desc()) 排序(默认升序)[加上.desc()变成降序]
        # matchedStatus 匹配状态, 0 未匹配  1 部分匹配
        debt_list = Debt_info.query.filter(or_(Debt_info.matchedStatus == 0, Debt_info.matchedStatus == 1)) \
            .order_by(Debt_info.id).all()
        self.debtDeque = MatchQueue(debt_list)  # 待匹配的债权 栈

        # 未匹配或部分匹配的资金列表    matchedStatus 匹配状态, 0 未匹配  1 部分匹配
        fund_list = Funding_not_matched.query \
            .filter(or_(Funding_not_matched.matchedStatus == 0, Funding_not_matched.matchedStatus == 1)) \
            .order_by(Funding_not_matched.fId).all()
        self.fundsDeque = MatchQueue(fund_list)  # 待匹配的资金 栈

    def start_match(self):
        """正式开始撮合匹配"""
        try:
            # 循环  待匹配的债权的栈 和 待匹配的资金的栈
            while (not self.debtDeque.empty) and (not self.fundsDeque.empty):
                # get_item  从栈中取一条待匹配的数据
                debt_item = self.debtDeque.get_item()
                fund_item = self.fundsDeque.get_item()
                # 匹配有三种情况, 第一: 债权资金金额 > 理财资金金额, 第二: 债权资金金额 < 理财资金金额, 第三: 债权资金金额 = 理财资金金额
                # debtMoney 债权金额        fNotMatchedMoney 待匹配金额
                if debt_item.debtMoney > fund_item.fNotMatchedMoney:
                    self.debtMoneyMoreThanFunds(debt_item, fund_item)
                elif debt_item.debtMoney < fund_item.fNotMatchedMoney:
                    self.debtMoneyLessThanFunds(debt_item, fund_item)
                else:
                    self.debtMoneyEqualFunds(debt_item, fund_item)

            db.session.commit()  # 提交数据库
            return {'msg': '匹配完成'}
        except Exception as e:
            current_app.logger.error(e)  # 将异常输入到日志中
            db.session.rollback()  # 数据回滚
            return {'message': '服务器忙，请稍后重试'}, 500

    # 债权资金部分匹配的业务逻辑: 债权资金金额 > 理财资金金额
    def debtMoneyMoreThanFunds(self, debt, funds):
        """待匹配的债权资金 > 待匹配的投资理财的资金"""
        # 新增一条匹配结果
        self.createMatchedResult(debt, funds, funds.fNotMatchedMoney)
        # matchedStatus: 匹配状态      0 未匹配  1 部分匹配  2 完全匹配
        debt.debtMoney -= funds.fNotMatchedMoney  # 债权剩下未匹配的金额
        debt.matchedStatus = 1  # 修改债权的匹配状态为1

        debt.matchedMoney += funds.fNotMatchedMoney  # 修改债权的已匹配金额

        funds.fNotMatchedMoney = 0  # 匹配完成后，修改投资理财的待匹配金额
        funds.matchedStatus = 2  # 修改投资理财的匹配状态为2

        # 债权还剩下一点资金未匹配，马上重新插入到栈中  debtDeque 待匹配的债权的栈
        # PUSH 指令 PUSH 指令首先减少 ESP 的值，再将源操作数复制到堆栈。
        self.debtDeque.push(debt)

        # 投资理财的金额已全部匹配完了，所以可以开始产生收益
        self.createExpectedResult(funds)

    def debtMoneyLessThanFunds(self, debt, funds):
        """待匹配的债权资金 < 待匹配的投资理财的资金"""
        funds.fNotMatchedMoney -= debt.debtMoney  # 投资资金剩余未匹配金额
        funds.matchedStatus = 1  # 修改投资理财的匹配状态为1
        self.createMatchedResult(debt, funds, debt.debtMoney)  # 新增一条匹配结果
        print('debt.matchedMoney', debt)
        debt.matchedMoney += debt.debtMoney  # 修改债权的已匹配金额
        debt.debtMoney = 0  # 匹配完成后，修改债权的待匹配金额
        print('debt.user', debt.user)
        debt.user.accountInfo.balance += debt.repaymenMoney  # 帐户可用余额 += 还款金额
        debt.matchedStatus = 2  # 修改债权的匹配状态为2
        self.startRepayPlay(debt)  # 正式开始还款
        # 投资理财还剩下一点资金未匹配，马上重新插入到栈中  fundsDeque 待匹配的资金的栈
        self.fundsDeque.push(funds)

    def debtMoneyEqualFunds(self, debt, funds):
        """待匹配的债权资金 = 待匹配的投资理财的资金   匹配状态: 0 未匹配  1 部分匹配  2 完全匹配"""
        debt.matchedStatus = 2  # 修改债权的匹配状态为2
        debt.matchedMoney += debt.debtMoney  # 修改债权中的已匹配金额
        self.createMatchedResult(debt, funds, debt.debtMoney)  # 新增一条匹配结果
        debt.debtMoney = 0  # 匹配完成后，修改债权的待匹配金额
        funds.fNotMatchedMoney = 0  # 匹配完成后，修改投资理财的待匹配金额
        funds.matchedStatus = 2  # 修改投资理财的匹配状态为2
        self.createExpectedResult(funds)  # 正式开始产生收益
        self.startRepayPlay(debt)  # 正式开始还款

    def startRepayPlay(self, debt):
        """还款计划正式生效"""
        repay_list = debt.debtor_record  # 还款计划列表
        for i in range(len(repay_list)):
            repay_date = datetime.now() + relativedelta(months=(i + 1))  # i+1 还款期数
            # 修改还款计划中的还款时间      receivableDate 应还日期
            repay_list[i].receivableDate = repay_date

    def createMatchedResult(self, debt, funds, match_money):
        """创建一条匹配结果数据，并保存到数据库中
            debt 传入待匹配的债权资金 ， funds 待匹配的投资理财的资金， match_money 匹配金额(谁完全匹配就传谁)
        """
        # 生成交易流水号
        match_num = gen_trans_id(DealType.match.name)
        # 匹配结果表的模型类(投资人的用户id, 债权id, 投资记录id,    交易流水号, 购买金额（匹配金额）,
        #                购买理财或者产生借款确认的日期）, 是否确认（0否认1确认）, 匹配上的日期(当前系统时间))
        result = Matched_result(userId=funds.investRecord.pUid, debtId=debt.id, investId=funds.investRecord.pId,
                                transferSerialNo=match_num, purchaseMoney=match_money,
                                confirmedDate=funds.investRecord.pDate, isConfirmed=1, matchDate=datetime.now())
        db.session.add(result)  # 添加数据库

    def createExpectedResult(self, funds):
        """创建一条预期收益的记录"""
        # 预期收益表的模型类(投资用户ID, 产品ID,    投资记录ID,   收益日期(当前系统时间+投资期限(期数)),    收益金额)
        ex_return = Expected_return(userId=funds.investRecord.pUid, productId=funds.investRecord.pProductId,
                                    investRecord=funds.investRecord.pId,
                                    expectedDate=datetime.now() + relativedelta(months=funds.investRecord.pDeadline),
                                    expectedMoney=funds.investRecord.pProspectiveEarnings)
        # 修改用户投资记录状态     1 表示正式产生收益,2 收益结束 投资结束
        funds.investRecord.pStatus = 1
        # 修改投资记录的开始计算利息时间(当前系统时间)
        funds.investRecord.pInterestStartDate = datetime.now()
        db.session.add(ex_return)  # 添加数据库


class MatchUp(Resource):

    def post(self):
        match = Match()
        return match.start_match()
